TiDB Cloud APIを使って、請求情報をEventBridge Scheduler+Lambda+SNSで定期通知してみた
こんにちは、ゲームソリューション部のsoraです。
今回は、TiDB Cloud APIを使って、請求情報をEventBridge Scheduler+Lambda+SNSで定期通知してみたことについて書いていきます。
構成
EventBridge Schedulerで指定した間隔でLambdaを呼び出して、TiDB Cloud APIを使っての請求情報を取得して、SNS経由でSlack通知する構成です。
Lambda(Python)コード
今回の構成では、TiDB Cloud APIから情報取得する用とSlack通知用の2つがあります。
ディレクトリ構成は以下です。
. ├── lambda │ ├── slack_notification.py │ └── tidb-api │ ├── dist │ │ └── tidb_api.py │ └── requirements.txt ├── main.tf └── terraform.tfvars
TiDB Cloud APIから情報取得するLambdaコードは以下です。
月初と月中で情報を取得することを想定しています。
※テストのためTiDBのAPIキーをLambdaに書くようにしていますが、本来だと良くないためSecrets Managerなどで管理するようにしてください。
TiDB Cloud APIについては、以下をご参照ください。
Billing System OPENAPI (v1beta1)
import boto3 import json import os import requests from requests.auth import HTTPDigestAuth import datetime from dateutil.relativedelta import relativedelta from zoneinfo import ZoneInfo SNSTopicArn = os.environ['SNSTopicArn'] TiDBAPIPublicKey = os.environ['TiDBPublicKey'] TiDBAPIPrivateKey = os.environ['TiDBPrivateKey'] def lambda_handler(event, context): public_key = TiDBAPIPublicKey private_key = TiDBAPIPrivateKey # 実行する年月を取得 date_source = datetime.datetime.now(ZoneInfo("Asia/Tokyo")).date() if date_source.day == 1: date_source = date_source - relativedelta(months=1) date = str(date_source) year_month = date[0:7] r_get = requests.get('https://billing.tidbapi.com/v1beta1/bills/' + year_month, auth=HTTPDigestAuth(public_key, private_key)) billing_source = r_get.json() print(billing_source) message = [] message.append("TiDB Cloudの利用料を通知します。") message.append(f"利用年月: {billing_source['overview']['billedMonth']} ,利用料: {billing_source['overview']['runningTotal']}") if billing_source['overview']['runningTotal'] != "0": for project in billing_source['summaryByProject']['projects']: message.append(f"プロジェクト名: {project['projectName']} ,プロジェクト利用料: {project['runningTotal']}") print(message) # メッセージをSNSに送信 # 通知を送信するSNSトピックのARNを指定 sns_client = boto3.client('sns') sns_topic_arn = SNSTopicArn sns_client.publish( TopicArn=sns_topic_arn, Message=json.dumps(message, indent=3, ensure_ascii=False) )
Pythonのライブラリはrequirements.txtを使って取得します。
requests==2.31.0 datetime==5.2 python-dateutil==2.8.2 urllib3==1.26.17
boto3を使うときに、urlib3 2.xを使っていると以下エラーが出たため、urlib3 1.xを指定しています。
cannot import name 'DEFAULT_CIPHERS' from 'urllib3.util.ssl_
参考:"cannot import name 'DEFAULT_CIPHERS' from 'urllib3.util.ssl_'"の原因
Slack通知用のLambdaは、色々なところで紹介されていると思いますので割愛します。
Terraformコード
Terraformを使って作成します。
variable tidb_public_key {} variable tidb_private_key {} variable slack_webhook_url {} variable slack_channel_name {} terraform { #AWSプロバイダーのバージョン指定 required_providers { aws = { source = "hashicorp/aws" version = "~> 5.19.0" } } #tfstateファイルをS3に配置する(配置先のS3は事前に作成済み) backend s3 { bucket = "<S3 bucket名>" region = "ap-northeast-1" key = "tidb-api.tfstate" } } #AWSプロバイダーの定義 provider aws { region = "ap-northeast-1" } #============ IAMロール作成 start ============ ##EventBridge Sheduler data aws_iam_policy_document events_assume_role { statement { effect = "Allow" principals { type = "Service" identifiers = ["scheduler.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource aws_iam_role iam_for_events { name = "EventBridge_TiDB_API_Role" assume_role_policy = data.aws_iam_policy_document.events_assume_role.json managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaRole"] } ##Lambda(TiDB APIの情報取得用) data aws_iam_policy_document lambda_assume_role { statement { effect = "Allow" principals { type = "Service" identifiers = ["lambda.amazonaws.com"] } actions = ["sts:AssumeRole"] } } resource aws_iam_role iam_for_lambda { name = "Lambda_TiDB_API_Role" assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json inline_policy { name = "my_inline_policy" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = ["sns:Publish"] Effect = "Allow" Resource = "*" }, ] }) } } #============ IAMロール作成 end ============ #============ リソース作成 start ============ ##SNS(トピック) ###トピックの作成 resource aws_sns_topic tidb_api_topic { name = "TiDB-API-topic" } data aws_iam_policy_document sns_assume_role { statement { effect = "Allow" principals { type = "Service" identifiers = ["lambda.amazonaws.com"] } actions = ["sns:Publish"] resources = [aws_sns_topic.tidb_api_topic.arn] } } resource aws_sns_topic_policy sns_topic_policy { arn = aws_sns_topic.tidb_api_topic.arn policy = data.aws_iam_policy_document.sns_assume_role.json } ##Lambda(TiDB APIの情報取得用) data archive_file lambda_tidbapi { type = "zip" source_dir = "lambda/tidb-api/dist" output_path = "tidb_api.zip" } resource aws_lambda_function lambda_tidbapi { filename = "tidb_api.zip" function_name = "tidb-api-execution" role = aws_iam_role.iam_for_lambda.arn handler = "tidb_api.lambda_handler" source_code_hash = data.archive_file.lambda_tidbapi.output_base64sha256 runtime = "python3.10" timeout = 30 environment { variables = { SNSTopicArn = aws_sns_topic.tidb_api_topic.arn TiDBPublicKey = var.tidb_public_key TiDBPrivateKey = var.tidb_private_key } } } ##Lambda(Slack通知用) data archive_file lambda_slack { type = "zip" source_file = "lambda/slack_notification.py" output_path = "slack_notification.zip" } resource aws_lambda_function lambda_slack { filename = "slack_notification.zip" function_name = "tidb-api-to-slack" role = aws_iam_role.iam_for_lambda.arn handler = "slack_notification.lambda_handler" source_code_hash = data.archive_file.lambda_slack.output_base64sha256 runtime = "python3.9" #slack通知先 environment { variables = { HookURL = var.slack_webhook_url ChannelName = var.slack_channel_name } } } resource aws_lambda_permission lambda_slack_permission { action = "lambda:InvokeFunction" function_name = aws_lambda_function.lambda_slack.function_name principal = "sns.amazonaws.com" source_arn = aws_sns_topic.tidb_api_topic.arn } ##EventBridge Sheduler resource aws_scheduler_schedule eventbridge { name = "tidb-api-schedule" description = "TiDB API Notification" flexible_time_window { mode = "OFF" } schedule_expression = "cron(0 8 1,15 * ? *)" schedule_expression_timezone = "Asia/Tokyo" target { arn = aws_lambda_function.lambda_tidbapi.arn role_arn = aws_iam_role.iam_for_events.arn } } ##SNS(サブスクリプション) resource aws_sns_topic_subscription slack_subscription { topic_arn = aws_sns_topic.tidb_api_topic.arn protocol = "lambda" endpoint = aws_lambda_function.lambda_slack.arn } #============ リソース作成 end ============
デプロイ
Pythonのライブラリを取得した後に、Terraformを実行します。
$ pwd /xxxx/xxxx/tidb-api-lambda/lambda/tidb-api $ pip install -r requirements.txt --target \dist $ pwd /xxxx/xxxx/tidb-api-lambda $ terraform init $ terraform apply
動作確認
テストを実行するとSlackに通知が来ることが確認できました。
最後に
今回は、TiDB Cloud APIを使って、請求情報をEventBridge Scheduler+Lambda+SNSで定期通知してみたことを記事にしました。
どなたかの参考になると幸いです。